﻿////////////////////////////////////////////////////
// GLOBALS
////////////////////////////////////////////////////

var seatingLoaded = false;

////////////////////////////////////////////////////
// CONSTANTS
////////////////////////////////////////////////////

var EMPTY = 0,      // Available
    SOLD = 1,       // Previously sold
    SPECIAL = 3,    // Special / Wheelchair seat
    SELECTED = 4;   // Selected by user

////////////////////////////////////////////////////
// CUSTOM SEATING POPUP FUNCTIONALITY
////////////////////////////////////////////////////

var popupTimer;

// Hides the validation popup
function hidePopup() {
    $('.Seating-Popup').fadeOut(200);
}

// Shows the validation popup with the given message
function showPopup(message) {
    var seatingPopup = $('.Seating-Popup'),
        page = $(document),
        parentPage = $(parent.document),
        yPosition = 0,
        xPosition = 0,
        opacity = 0.85;

    clearTimeout(popupTimer);

    if (parentPage.length === 1) { // In an iframe
        yPosition = $('.Seating-Theatre').height() * (parentPage.scrollTop() / parentPage.height()); // Offset the popup with the scroll position of the page
    }
    else {
        yPosition = $('.Seating-Theatre').height() * (page.scrollTop() / page.height()); // Offset the popup with the scroll position of the page
    }
    xPosition = seatingPopup.parent().scrollLeft();    

    seatingPopup.find('.Seating-PopupMessage').text(message); // Set the message text
    seatingPopup.css('filter', 'alpha(opacity=' + opacity * 100 + ')'); // Fixes IE 8 font problem
    seatingPopup.css('opacity', opacity);
    seatingPopup.fadeIn(200, function () { popupTimer = setTimeout(function () { hidePopup(); }, 7000); });
    seatingPopup.css('top', yPosition);
    seatingPopup.css('left', xPosition);
}

////////////////////////////////////////////////////
// SEAT SELECTION FUNCTINALITY
////////////////////////////////////////////////////

// Gets the html 'img' object on the page for a given position by matching its data tags
function getSeatImage(areaNumber, row, col) {
    return $('.Seating-Area[data-area-number=\'' + areaNumber + '\'] img[data-row=\'' + row + '\'][data-col=\'' + col + '\']');
}

// Get the area from the theatre
function getArea(areaNumber) {
    var i = 0;

    for (i = 0; i < m_theatre.Areas.length; i++ ) {
        if (m_theatre.Areas[i].AreaNumber === areaNumber) {
            return m_theatre.Areas[i];
        }
    }

    return null;
}

// Get the seat from the theatre
function getSeat(areaNumber, row, column) {
    var area, seat;

    area = getArea(areaNumber);

    if (column >= 0 && column < area.ColumnCount && row >= 0 && row < area.Rows.length) { // Check bounds
        seat = area.Rows[row].Seats[column];
    }

    if (seat) { // Seat was found
        seat.area = area; // Attach a reference to the area to the seat
    }

    return seat;
}

function seatAvailable(seat) {
    if (seat.Status === EMPTY) {
        return true;
    }
    else if (seat.Status === SPECIAL && m_validationRules.SellSpecialSeats) {
        return true;
    }
}

// Add a seat to the selected seat list
// Note: There is a selected seat list per area category. The selected seat list holds a basic representation
// of each seat that is currently selected.
function addSeatToSelectedList(seat) {
    var seatSelectionObject = { "RowIndex": seat.RowIndex, "ColumnIndex": seat.ColumnIndex, "AreaNumber": seat.area.AreaNumber },
        i = 0;

    for (i = 0; i < m_selectedSeats.length; i++) {
        if (m_selectedSeats[i].AreaCategoryCode === seat.area.AreaCategoryCode) {
            m_selectedSeats[i].Seats.unshift(seatSelectionObject); // Push to front
            return;
        }
    }
}

// Gets the list of selected seats for a given area category code
function getSelectedSeatList(areaCategoryCode) {
    var i = 0;

    for (i = 0; i < m_selectedSeats.length; i++) {
        if (m_selectedSeats[i].AreaCategoryCode === areaCategoryCode) {
            return m_selectedSeats[i].Seats;
        }
    }
}

function removeSeatFromSelectedList(seat) {
    var selectedSeats = getSelectedSeatList(seat.area.AreaCategoryCode),
        i = 0;

    for (i = 0; i < selectedSeats.length; i++) {
        if (selectedSeats[i].RowIndex === seat.RowIndex && selectedSeats[i].ColumnIndex === seat.ColumnIndex && selectedSeats[i].AreaNumber === seat.area.AreaNumber) {
            selectedSeats.splice(i, 1); // Remove from selected seats array
            return;
        }
    }
}

function selectSeat(areaNumber, row, col) {
    var seat = getSeat(areaNumber, row, col),
        seatImage = getSeatImage(areaNumber, row, col);

    m_theatre.AreaCategories[seat.area.AreaCategoryCode].SeatsNotAllocatedCount -= 1; // Reduce the number of available seats

    seat.Status = SELECTED;

    // Change to the reserved image
    if (seat.OriginalSeatStatus === EMPTY || seat.OriginalSeatStatus === SELECTED) {
        seatImage.attr('src', seatImage.attr('src').replace('seat_0', 'seat_5'));
    }
    else if (seat.OriginalSeatStatus === SPECIAL) {
        seatImage.attr('src', seatImage.attr('src').replace('seat_4', 'seat_5'));
    }

    addSeatToSelectedList(seat);
}

// Deselects a specific seat
function deselectSeat(areaNumber, row, col) {
    var seat = getSeat(areaNumber, row, col),
        seatImage = getSeatImage(areaNumber, row, col);

    m_theatre.AreaCategories[seat.area.AreaCategoryCode].SeatsNotAllocatedCount += 1; // Increase the number of available seats for this area category

    if (seat.OriginalSeatStatus === EMPTY || seat.OriginalSeatStatus === SELECTED) {
        seatImage.attr('src', seatImage.attr('src').replace('seat_5', 'seat_0')); // Change to the empty image TODO: Revisit how changing images works
        seat.Status = EMPTY;
    }
    else if (seat.OriginalSeatStatus === SPECIAL) {
        seatImage.attr('src', seatImage.attr('src').replace('seat_5', 'seat_4'));
        seat.Status = SPECIAL;
    }

    removeSeatFromSelectedList(seat);
}

// Deselects the currently selected seat which is furthest from the chosen seat
function deselectFurthestSeat(areaNumber, row, col) {
    var area = getArea(areaNumber),
        selectedSeats = getSelectedSeatList(area.AreaCategoryCode),
        i,
        furthestSeatSoFar;

    for (i = 0; i < selectedSeats.length; i++) {
        if (!furthestSeatSoFar) { // No seat chosen yet
            furthestSeatSoFar = selectedSeats[i];
            continue;
        }

        if(selectedSeats[i].AreaNumber !== areaNumber) { // Area number takes precedence
            furthestSeatSoFar = selectedSeats[i];
            break;
        }
        else if (Math.abs(selectedSeats[i].RowIndex - row) > Math.abs(furthestSeatSoFar.RowIndex - row)) { // Row difference takes precedence over column difference
            furthestSeatSoFar = selectedSeats[i];
        }
        else if (Math.abs(selectedSeats[i].RowIndex - row) === Math.abs(furthestSeatSoFar.RowIndex - row) &&
                 Math.abs(selectedSeats[i].ColumnIndex - col) > Math.abs(furthestSeatSoFar.ColumnIndex - col)) {
            furthestSeatSoFar = selectedSeats[i];
        }
    }

    deselectSeat(furthestSeatSoFar.AreaNumber, furthestSeatSoFar.RowIndex, furthestSeatSoFar.ColumnIndex);
}

// Selects as many seats as available in the sofa that this seat belongs to
function selectSofa(seat, numberToAllocate) {
    var i,
        sofaSeat,
        seatsToLeft = 0,
        seatsToRight = 0,
        soldSeatsToLeft = 0,
        soldSeatsToRight = 0,
        sofaIsEmpty = true,
        allocateFromRight = true;

    // Work out how many seats are to the left and right of the chosen seat on the sofa - assuming IDs are ordered with higher to the right
    for (i = 0; i < seat.SeatsInGroup.length; i++) {
        sofaSeat = getSeat(seat.area.AreaNumber, seat.SeatsInGroup[i].RowIndex, seat.SeatsInGroup[i].ColumnIndex);

        if (!seatAvailable(sofaSeat)) {
            sofaIsEmpty = false;

            if (sofaSeat.ColumnIndex > seat.ColumnIndex) { // Seat indices go from right to left
                soldSeatsToLeft++;
            }
            else if (sofaSeat.ColumnIndex < seat.ColumnIndex) {
                soldSeatsToRight++;
            }
        }

        if (sofaSeat.ColumnIndex > seat.ColumnIndex) {
            seatsToLeft++;
        }
        else if (sofaSeat.ColumnIndex < seat.ColumnIndex) {
            seatsToRight++;
        }
    }

    if (sofaIsEmpty) {
        if (seatsToLeft < seatsToRight) { // Seat is on the left side of the sofa - Allocate from left
            allocateFromRight = false;
        }
    }
    else { // Sofa has other seats allocated
        if (soldSeatsToLeft > soldSeatsToRight) { // There are more sold seats on the left - Allocate from left
            allocateFromRight = false;
        }
    }

    if (allocateFromRight) {
        for (i = 0; i < seat.SeatsInGroup.length && numberToAllocate !== 0; i++) {
            sofaSeat = getSeat(seat.area.AreaNumber, seat.SeatsInGroup[i].RowIndex, seat.SeatsInGroup[i].ColumnIndex);

            if (seatAvailable(sofaSeat)) {
                selectSeat(seat.area.AreaNumber, sofaSeat.RowIndex, sofaSeat.ColumnIndex);
                numberToAllocate--;
            }
        }
    }
    else {
        for (i = seat.SeatsInGroup.length - 1; i >= 0 && numberToAllocate !== 0; i--) {
            sofaSeat = getSeat(seat.area.AreaNumber, seat.SeatsInGroup[i].RowIndex, seat.SeatsInGroup[i].ColumnIndex);

            if (seatAvailable(sofaSeat)) {
                selectSeat(seat.area.AreaNumber, sofaSeat.RowIndex, sofaSeat.ColumnIndex);
                numberToAllocate--;
            }
        }
    }
}

// Deselects the whole sofa that a seat belongs to
function deselectSofa(seat) {
    var i, sofaSeat;

    for (i = 0; i < seat.SeatsInGroup.length; i++) {
        sofaSeat = getSeat(seat.area.AreaNumber, seat.SeatsInGroup[i].RowIndex, seat.SeatsInGroup[i].ColumnIndex);

        if (sofaSeat.Status === SELECTED) { // Seat is reserved
            deselectSeat(seat.area.AreaNumber, sofaSeat.RowIndex, sofaSeat.ColumnIndex);
        }
    }
}

// Gets the number of seats available in the sofa that the seat is part of
function getNumAvailableSeatsInSofa(seat) {
    var i,
        seatsAvailable = 0;

    for (i = 0; i < seat.SeatsInGroup.length; i++) {
        var sofaSeat = getSeat(seat.area.AreaNumber, seat.SeatsInGroup[i].RowIndex, seat.SeatsInGroup[i].ColumnIndex);

        if (seatAvailable(seat)) {
            seatsAvailable++;
        }
    }

    return seatsAvailable;
}

// Deselects all seats in the given area category
function deselectAllSeats(areaCategoryCode) {
    var i,
        selectedSeats = getSelectedSeatList(areaCategoryCode).slice(); // Take a copy of this array as 'deselectSeat' edits this array directly which would break the for loop

    for (i = 0; i < selectedSeats.length; i++) {
        deselectSeat(selectedSeats[i].AreaNumber, selectedSeats[i].RowIndex, selectedSeats[i].ColumnIndex);
    }
}

// Deselects a specific number of seats in an area category
// If the seat passed as a parameter is on a sofa then don't deselect seats on the same sofa
function deselectSeats(seat, seatsToDeselect) {
    var i,
        selectedSeats = getSelectedSeatList(seat.area.AreaCategoryCode).slice(); // Take a copy of this array as 'deselectSeat' edits this array directly which would break the for loop

    if (selectedSeats.length === 0) {
        return;
    }

    if (seatsToDeselect >= selectedSeats.length) {
        deselectAllSeats(seat.area.AreaCategoryCode);
    }
    else {
        for (i = 0; i < seatsToDeselect; i++) {
            deselectFurthestSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex);
        }
    }
}

// This function is called by the area clicked event
function seatClicked(seatImage, areaNumber) {
    var seat = getSeat(areaNumber, seatImage.data('row'), seatImage.data('col')),
        message = '';

    if (!seat) { // Place holder seat
        return;
    }

    if (m_theatre.AreaCategories[seat.area.AreaCategoryCode].SeatsToAllocate === 0) { // No seats available in this area category
        return m_validationRules.InvalidAreaSelected;
    }
    else if (seatAvailable(seat)) {
        var seatsRemaining = m_theatre.AreaCategories[seat.area.AreaCategoryCode].SeatsNotAllocatedCount;

        if (seatsRemaining === 0) { // No seats left in this area category
            if (m_selectionMode === 'seatbyseat') {
                return m_validationRules.AllSeatsAllocated;
            }
        }

        if (seat.SeatsInGroup && m_sofaSeatingEnabled) { // Seat is in a sofa
            var seatsAvailable;

            if (m_selectionMode === 'seatbyseat') {
                seatsAvailable = m_theatre.AreaCategories[seat.area.AreaCategoryCode].SeatsNotAllocatedCount;

                // Check whether the whole sofa can be selected with the seats available in this area category
                if (seatsAvailable < getNumAvailableSeatsInSofa(seat)) { // Can't select whole sofa
                    if (m_validationRules.MustFillSofa) {
                        message = m_validationRules.MustFillSofa;
                    }
                }

                selectSofa(seat, seatsAvailable);
            }
            else if (m_selectionMode === 'sequential') {
                deselectSofa(seat); // The user may already have selected seats on this sofa

                seatsRemaining = m_theatre.AreaCategories[seat.area.AreaCategoryCode].SeatsNotAllocatedCount;
                seatsAvailable = m_theatre.AreaCategories[seat.area.AreaCategoryCode].SeatsToAllocate;

                var seatsAvailableInSofa = getNumAvailableSeatsInSofa(seat);

                // Check whether the whole sofa can be selected with the seats available in this area category
                if (seatsAvailable < seatsAvailableInSofa) { // Can't select whole sofa
                    if (m_validationRules.MustFillSofa) {
                        return m_validationRules.MustFillSofa;
                    }
                }

                if (seatsAvailableInSofa - seatsRemaining > 0) {
                    deselectSeats(seat, seatsAvailableInSofa - seatsRemaining); // Deselect as many seats as needed to fill the sofa
                }

                selectSofa(seat, Math.min(seatsAvailableInSofa, seatsAvailable));
            }
        }
        else { // Standard seat
            if (m_selectionMode === 'sequential') {
                var selectedSeats = getSelectedSeatList(seat.area.AreaCategoryCode);

                if (selectedSeats.length === m_theatre.AreaCategories[seat.area.AreaCategoryCode].SeatsToAllocate) {
                    deselectSeat(selectedSeats[selectedSeats.length - 1].AreaNumber, selectedSeats[selectedSeats.length - 1].RowIndex, selectedSeats[selectedSeats.length - 1].ColumnIndex);
                }
            }

            if (seat.Status === SPECIAL) {
                message = m_validationRules.WheelChairSelected;
            }

            selectSeat(areaNumber, seatImage.data('row'), seatImage.data('col'));
        }
    }
    else if (seat.Status === SELECTED) { // The seat is already selected
        if (m_selectionMode === 'sequential') {
            return; // Deselection isn't available in sequential movement mode
        }

        if (seat.SeatsInGroup && m_sofaSeatingEnabled) { // Seat is in a sofa
            deselectSofa(seat); // Deselect whole sofa
        }
        else { // Standard seat
            deselectSeat(areaNumber, seatImage.data('row'), seatImage.data('col'));
        }
    }
    else {
        return m_validationRules.InvalidSeatSelected;
    }

    // If the user is selecting a sales server allocated seat then don't show a validation warning
    if (seat.OriginalSeatStatus === SELECTED) {
        message = '';
    }

    return message;
}

////////////////////////////////////////////////////
// VALIDATION FUNCTINALITY
////////////////////////////////////////////////////

// Object to encapsulate a seating rule with a validation method and message to display upon failure
function SeatRule(msg, validationMethod) {
    this.message = msg;
    this.validate = validationMethod;
}

// Indicates which seat is causing the seating error TODO: Enable/disable this based on a setting on the control?
//function flashSeat(seat) {
//    getSeatImage(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex).effect('highlight', { color: '#FF0000' }, 1000);
//}

//function flashSofa(seat) {
//    var sofaSeat;

//    for (i = 0; i < seat.SeatsInGroup.length; i++) {
//        sofaSeat = getSeatById(seat.area, seat.RowIndex, seat.SeatsInGroup[i]);

//        flashSeat(sofaSeat);
//    }
//}

// Checks for a gap seat next to a selected seat and then for a selected seat on the other side of the gap
function checkSingleSeatBetweenSeats(seat) {

    // If we are ignoring special seats then we need to 
    // skip validation if the seat in question is special.
    if (m_validationRules.SpecialSeatsIgnoreLogic) {
        if (seat.OriginalSeatStatus === SPECIAL) {
            return true;
        }
    }

    var oneSeatOver, twoSeatsOver;

    // Check to the left
    if (seat.ColumnIndex + 2 < seat.area.ColumnCount) { // Check row bounds
        oneSeatOver = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex + 1);

        if(oneSeatOver && seatAvailable(oneSeatOver)) {
            twoSeatsOver = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex + 2);

            if(twoSeatsOver) {
                if (twoSeatsOver.Status === SELECTED) {
                    //flashSeat(oneSeatOver);
                    return false;
                }
            }
        }
    }

    // Check to the right
    if (seat.ColumnIndex - 2 > -1) {
        oneSeatOver = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex - 1);

        if (oneSeatOver && seatAvailable(oneSeatOver)) {
            twoSeatsOver = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex - 2);

            if (twoSeatsOver) {
                if (twoSeatsOver.Status === SELECTED) {
                    //flashSeat(oneSeatOver);
                    return false;
                }
            }
        }
    }

    return true;
}

// Ensure that a single seat gap isn't left between selected seats and the aisle
function checkSingleSeatGapFromAisle(seat) {

    // If we are ignoring special seats then we need to 
    // skip validation if the seat in question is special.
    if (m_validationRules.SpecialSeatsIgnoreLogic) {
        if (seat.OriginalSeatStatus === SPECIAL) {
            return true;
        }
    }

    var oneSeatOver;

    // Check if aisle to the left
    if (seat.ColumnIndex + 1 < seat.area.ColumnCount) { // Check row bounds
        oneSeatOver = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex + 1);

        if (oneSeatOver && seatAvailable(oneSeatOver)) {
            if (seat.ColumnIndex + 2 === seat.area.ColumnCount || !getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex + 2)) {
                //flashSeat(oneSeatOver);
                return false;
            }
        }
    }

    // Check if aisle to the right
    if (seat.ColumnIndex - 1 > -1) { // Check row bounds
        oneSeatOver = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex - 1);

        if (oneSeatOver && seatAvailable(oneSeatOver)) {
            if (seat.ColumnIndex - 2 === seat.area.ColumnCount || !getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex - 2)) {
                //flashSeat(oneSeatOver);
                return false;
            }
        }
    }

    return true;
}

// Ensure that a single seat isn't left between selected seats and other sold seats
function checkSingleSeatGapFromSoldSeats(seat) {

    // If we are ignoring special seats then we need to 
    // skip validation if the seat in question is special.
    if (m_validationRules.SpecialSeatsIgnoreLogic) {
        if (seat.OriginalSeatStatus === SPECIAL) {
            return true;
        }
    }

    var oneSeatOver, twoSeatsOver;
    
    // Check to the left
    if (seat.ColumnIndex + 2 < seat.area.ColumnCount) { // Check row bounds
        oneSeatOver = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex + 1);

        if (oneSeatOver && seatAvailable(oneSeatOver)) {
            twoSeatsOver = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex + 2);

            if (twoSeatsOver) {
                if (twoSeatsOver.Status === SOLD) {
                    //flashSeat(oneSeatOver);
                    return false;
                }
            }
        }
    }

    // Check to the right
    if (seat.ColumnIndex - 2 > -1) {
        oneSeatOver = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex - 1);

        if (oneSeatOver && seatAvailable(oneSeatOver)) { // Gap seat
            twoSeatsOver = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex - 2);

            if (twoSeatsOver) {
                if (twoSeatsOver.Status === SOLD) {
                    //flashSeat(oneSeatOver);
                    return false;
                }
            }
        }
    }

    return true;
}

// Ensure all sofas with allocated seats on them are full
function checkSofaFull(seat) {
    var i, sofaSeat;

    if (seat.SeatsInGroup && m_sofaSeatingEnabled) { // Seat is in a sofa
        for (i = 0; i < seat.SeatsInGroup.length; i++) {
            sofaSeat = getSeat(seat.area.AreaNumber, seat.SeatsInGroup[i].RowIndex, seat.SeatsInGroup[i].ColumnIndex);

            if (seatAvailable(sofaSeat)) {
                //flashSofa(seat);
                return false; // Ensure the whole sofa is reserved / sold
            }
        }
    }

    return true;
}

function checkCompanionSeats(seat) {
    var seatToLeft, seatToRight;

    if (seat.OriginalSeatStatus !== SPECIAL) {
        seatToLeft = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex + 1);
        seatToRight = getSeat(seat.area.AreaNumber, seat.RowIndex, seat.ColumnIndex - 1);

        if (seatToLeft && seatToLeft.Status === SPECIAL) {
            return false;
        }
        else if (seatToRight && seatToRight.Status === SPECIAL) {
            return false;
        }
    }

    return true;
}

// TODO: Re-visit how this works and make it clearer for the next person
function getActiveValidationRules() {
    var validationRules = [];

    if (m_validationRules.SingleSeatBetweenSeats) {
        validationRules.push(new SeatRule(m_validationRules.SingleSeatBetweenSeats, checkSingleSeatBetweenSeats));
    }

    if (m_validationRules.SingleSeatGapFromAisle) {
        validationRules.push(new SeatRule(m_validationRules.SingleSeatGapFromAisle, checkSingleSeatGapFromAisle));
    }

    if (m_validationRules.SingleSeatGapFromSoldSeats) {
        validationRules.push(new SeatRule(m_validationRules.SingleSeatGapFromSoldSeats, checkSingleSeatGapFromSoldSeats));
    }

    if (m_validationRules.MustFillSofa) {
        validationRules.push(new SeatRule(m_validationRules.MustFillSofa, checkSofaFull));
    }

    if (m_validationRules.EnforceCompanionSeatRule) {
        validationRules.push(new SeatRule(m_validationRules.EnforceCompanionSeatRule, checkCompanionSeats));
    }

    return validationRules;
}

// When validation passes this function fills a field on the page with the selected seats string
function setSelectedSeatsField() {
    var hiddenField = $('#' + m_hiddenFieldId),
        seat,
        selectedSeatsData = '',
        selectedSeatCount = 0,
        i;

    // Create the selected seats string to pass back to the server
    for (i = 0; i < m_selectedSeats.length; i++) {
        for (j = 0; j < m_selectedSeats[i].Seats.length; j++) {
            seat = m_selectedSeats[i].Seats[j];

            selectedSeatsData += m_selectedSeats[i].AreaCategoryCode + '|' + seat.AreaNumber + '|' + (seat.RowIndex + 1) + '|' + (seat.ColumnIndex + 1) + '|';

            selectedSeatCount += 1;
        }
    }

    // Set the hidden fields value
    hiddenField.val('|' + selectedSeatCount + '|' + selectedSeatsData);
}

// Called by the page when the validate button is clicked
function validateSeating() {
    var seatsHaveChanged = false,
        validationRules = getActiveValidationRules(), // Get the current rules
        i, j, k, seat, result;

    // Check whether seating has been changed at all by the user
    for (i = 0; i < m_selectedSeats.length; i++) {
        // Check if there are seats remaining for allocation
        if (m_theatre.AreaCategories[m_selectedSeats[i].AreaCategoryCode].SeatsNotAllocatedCount > 0) {
            showPopup(m_validationRules.AllSeatsInOrderMustBeSelected);
            return false;
        }

        for (j = 0; j < m_selectedSeats[i].Seats.length; j++) {
            seat = getSeat(m_selectedSeats[i].Seats[j].AreaNumber, m_selectedSeats[i].Seats[j].RowIndex, m_selectedSeats[i].Seats[j].ColumnIndex);

            if (seat.OriginalSeatStatus !== seat.Status) { // Seating has changed
                seatsHaveChanged = true;
            }
        }
    }

    // Only need to do validation checks if seats have been changed
    if (seatsHaveChanged) {
        for (i = 0; i < m_selectedSeats.length; i++) {
            for (j = 0; j < m_selectedSeats[i].Seats.length; j++) {
                // Check the current seat against each of the current validation rules
                for (k = 0; k < validationRules.length; k++) {
                    seat = getSeat(m_selectedSeats[i].Seats[j].AreaNumber, m_selectedSeats[i].Seats[j].RowIndex, m_selectedSeats[i].Seats[j].ColumnIndex);

                    result = validationRules[k].validate(seat);

                    if (result === false) { // Validation failed
                        showPopup(validationRules[k].message);
                        return false;
                    }
                }
            }
        }
    }

    setSelectedSeatsField();

    return true; // Validation passed
}

////////////////////////////////////////////////////
// ZOOM FUNCTIONALITY
////////////////////////////////////////////////////

//var zoomedIn = true;

function scrollToSelectedSeats() {
    var selectedSeats = $('.Seating-Area td[src*="seat_1"]');

    if (selectedSeats.length !== 0) {
        selectedSeats[0].scrollIntoView();
    }
}

//// Scales the theatre so that the seat images are their original size
//function zoomIn() {
//    var theatreWidth,
//        containerDiv,
//        containerWidth,
//        resizeAmount;

//    zoomedIn = true;

//    containerDiv = $('.Seating-Container').parent();

//    theatreWidth = $('.Seating-Theatre').data('originalsize');
//    containerWidth = containerDiv.width();

//    // Calculate percentage to resize by
//    resizeAmount = theatreWidth / containerWidth;

//    containerDiv.find('img, div, table, tr, td').each(function () {
//        var element = $(this);

//        element.width(resizeAmount * element.width());
//        element.height(resizeAmount * element.height());
//    });
//}

//// Scales the theatre so that all of the seats are visible
//function zoomOut() {
//    var theatreWidth,
//        containerDiv,
//        containerWidth,
//        resizeAmount;

//    zoomedIn = false;

//    containerDiv = $('.Seating-Container').parent();

//    theatreWidth = $('.Seating-Theatre').data('originalsize');
//    containerWidth = containerDiv.width();

//    // Calculate percentage to resize by
//    resizeAmount = containerWidth / theatreWidth;

//    containerDiv.find('img, div, table, tr, td').each(function () {
//        var element = $(this);

//        element.width(resizeAmount * element.width());
//        element.height(resizeAmount * element.height());
//    });
//}

//// Toggles the zoom level
//function zoomSeating() {
//    if (zoomedIn) {
//        zoomOut();
//    }
//    else {
//        zoomIn();
//        scrollToSelectedSeats();
//    }
//}

////////////////////////////////////////////////////
// FIXES
////////////////////////////////////////////////////

function roundNumber(num, dec) {
    var result = String(Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec));
    if (result.indexOf('.') < 0) { result += '.'; }
    while (result.length - result.indexOf('.') <= dec) { result += '0'; }
    return parseFloat(result);
}

function fixRowSizes() {
    var areas = $('.Seating-Area'),
        areaHeight,
        rowHeight, area, areaLen,
        rows, row, rowsLen, i, j;

    for (j = 0, areaLen = areas.length; j < areaLen; j++) {
        area = $(areas[j]);
        rows = area.find('tr');

        areaHeight = area.parent().height() * (area[0].style.height.replace(/[^-\d\.]/g, '') / 100);

        rowHeight = roundNumber(areaHeight / rows.length, 2);

        for (i = 0, rowsLen = rows.length; i < rowsLen; i++) {
            row = $(rows[i]);

            row.css('line-height', rowHeight + 'px');
            row.css('height', rowHeight + 'px');
        }
    }
}

// Make sure the images are the correct size for their area
function fixImageSizes() {
    var areas = $('.Seating-Area'),
        areaWidth, areaHeight,
        imageWidth, imageHeight, area,
        areaContainer, columnCount, images,
        image, i, j, rowCount,
        areaLen, imageLen;

    for (j = 0, areaLen = areas.length; j < areaLen; j++) {
        area = $(areas[j]);
        images = area.find('img');
        columnCount = getArea(area.data('area-number')).ColumnCount;
        rowCount = area.find('tr').length;
        areaContainer = area.parent();

        areaWidth = areaContainer.width() * (area[0].style.width.replace(/[^-\d\.]/g, '') / 100);
        areaHeight = areaContainer.height() * (area[0].style.height.replace(/[^-\d\.]/g, '') / 100);

        imageWidth = areaWidth / columnCount;
        imageHeight = areaHeight / rowCount;

        for (i = 0, imageLen = images.length; i < imageLen; i++) {
            image = $(images[i]);

            image.css('width', Math.round(imageWidth) + 'px');
            image.css('height', Math.round(imageHeight) + 'px');
        }
    }
}

// Collapse all rows which don't contain an image
//function collapseEmptyRows() {
//    rows = $(areas[j]).find('tr:not(:has(img))');

//    for (i = 0, rowsLen = rows.length; i < rowsLen; i++) {
//        row = $(rows[i]);

//        row.css('line-height', '0px');
//        row.css('height', '0px');
//    }
//}

// Centers the areas in backwards compatibility mode
function centerAreas() {
    var theatre = $('.Seating-Theatre'),
        container = theatre.parent();

    theatre.css('position', 'relative');
    theatre.css('left', container.width() / 2 - theatre.width() / 2 + 'px');
}

// Fix seat name labels
//function setSeatNameWidths() {
//    var seatLabels = $('.Seating-Area p'),
//        label, i, len, labelParent;

//    for (i = 0, len = seatLabels.length; i < len; i++) {
//        label = $(seatLabels[i]);
//        labelParent = label.parent();

//        label.width(labelParent.width());
//        label.css('line-height', labelParent.height() + 'px');
//    }
//}

function setSeatNameWidths() {
    var areas = $('.Seating-Area'), seatLabels, firstLabel,
        label, i, len, parentWidth, parentHeight;

    for (i = 0, len = areas.length; i < len; i++) {
        seatLabels = $(areas[i]).find('p');

        firstLabel = $(seatLabels[0]);

        parentWidth = firstLabel.parent().width();
        parentHeight = firstLabel.parent().height() + 'px';

        for (j = 0, len2 = seatLabels.length; j < len2; j++) {
            label = $(seatLabels[j]);
            label.width(parentWidth);
            label.css('line-height', parentHeight);
        }
    }
}

////////////////////////////////////////////////////
// ENTRY POINT
////////////////////////////////////////////////////

$(function () {
    // Scroll to selected seats
    scrollToSelectedSeats(); //TODO: Implement?

    if (m_isLayout) {
        fixRowSizes();
        fixImageSizes();
        setSeatNameWidths();
    }
    else {
        //collapseEmptyRows(); // Can't use this as sometimes gap rows are in the middle of an area
        centerAreas();
    }

    setSeatNameWidths();

    // Bind click events to each area
    $('.Seating-Area').bind('click', function (event) {
        var seatImage,
            targetTagName = event.target.tagName.toLowerCase(),
            message;

        if (targetTagName !== 'img') { // Missed the image
            if (targetTagName === 'p') { // When seat names are enabled this will be the seat name
                seatImage = $(event.target).next(); // The next sibling after the seat name is the image
            }
            else if (targetTagName === 'td' || targetTagName === 'div') {
                seatImage = $(event.target).find('img');
            }
            else { // This could be the table, or tbody and can't be used
                return;
            }
        }
        else {
            seatImage = $(event.target);  // wraps the seat in a jQuery object
        }

        if (seatImage.length === 0) {
            return; // Seat image not found
        }

        message = seatClicked(seatImage, $(this).data('area-number'));

        if (message) {
            showPopup(message);
        }
        else {
            hidePopup();
        }
    });

    // Bind a click event to the error popup to hide it
    $('.Seating-Popup').bind('click', function (event) {
        hidePopup();
    });

    // Resize seating container
    var container = $('.Seating-Control > div');
    container.height(container[0].scrollHeight + 8); // Slight padding to always ensure no scrollbar (ff)

    initZoomButtons();

    // Show the seats
    $('.Seating-Container').css('visibility', 'visible');

    seatingLoaded = true;
});